#include "protocol.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "cJSON.h"
#include "log.h"

DEFINE_REQUEST_INIT(user_cmd);
DEFINE_REQUEST_INIT(get_status);
DEFINE_REQUEST_INIT(rtsp_to_rtmp);
DEFINE_REQUEST_INIT(rtsp_record);
DEFINE_REQUEST_INIT(rtsp_screenshot);
DEFINE_REQUEST_INIT(stream_probe);

static const char *const error_str[] = {
    "MEDIA_RET_SUCCESS",
    "MEDIA_RET_THREAD_ERROR",
    "MEDIA_RET_NULL_PTR",
    "MEDIA_RET_CMD_RUNNING",
    "MEDIA_RET_CMD_STOPED",
    "MEDIA_RET_CJSON_ERROR",
    "MEDIA_RET_MSG_HANDLE_ERROR",
    "MEDIA_RET_INTERNAL_ERROR",
    "MEDIA_RET_CMD_NOT_SURPPORT"
};

static media_t *media;

static msg_processing_t * request_init(request_t *req)
{
    msg_processing_t *msgp = NULL;
    if (strcmp(req->cmd, "user_cmd") == 0) {
        msgp = REQUEST_INIT(user_cmd);
    }
    else if (strcmp(req->cmd, "get_status") == 0) {
        msgp = REQUEST_INIT(get_status);
    }
    else if (strcmp(req->cmd, "rtsp_to_rtmp") == 0) {
        msgp = REQUEST_INIT(rtsp_to_rtmp);
    }
    else if (strcmp(req->cmd, "rtsp_record") == 0) {
        msgp =  REQUEST_INIT(rtsp_record);
    }
    else if (strcmp(req->cmd, "rtsp_screenshot") == 0) {
        msgp =  REQUEST_INIT(rtsp_screenshot);
    }
    else if (strcmp(req->cmd, "stream_probe") == 0) {
        msgp =  REQUEST_INIT(stream_probe);
    }
    return msgp;
}

int req_rtsp_to_rtmp_msg_to_do(msg_processing_t *msgp, response_t *resp)
{
    if (msgp == NULL || resp == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    req_rtsp_to_rtmp_t * rrtr = (req_rtsp_to_rtmp_t *)msgp;

    log_debug("rtmp url: %s", rrtr->rtmp_url);

    resp->payload.result = media->rtsp_to_rtmp(media, rrtr->run, rrtr->rtsp_url,
                                               rrtr->rtmp_url, rrtr->recode);
    return resp->payload.result;
}

int req_rtsp_to_rtmp_msg_to_struct(msg_processing_t *msgp, cJSON *obj)
{
    req_rtsp_to_rtmp_t *rrtr = (req_rtsp_to_rtmp_t *)msgp;

    cJSON *rtsp_to_rtmp_jo = obj;

    cJSON *run_json_obj = cJSON_GetObjectItem(rtsp_to_rtmp_jo, "run");
    if (!run_json_obj || !cJSON_IsBool(run_json_obj)) {
        return MEDIA_RET_CJSON_ERROR;
    } else {
        rrtr->run = cJSON_IsTrue(run_json_obj);
    }

    if (rrtr->run) {
        cJSON *rtsp_url_jo = cJSON_GetObjectItem(rtsp_to_rtmp_jo, "rtsp_url");
        if (!rtsp_url_jo || !cJSON_IsString(rtsp_url_jo)) {
            return MEDIA_RET_CJSON_ERROR;
        } else {
            strncpy(rrtr->rtsp_url, cJSON_GetStringValue(rtsp_url_jo), URL_LEN -1);
        }

        cJSON *rtmp_url_jo = cJSON_GetObjectItem(rtsp_to_rtmp_jo, "rtmp_url");
        if (!rtmp_url_jo || !cJSON_IsString(rtmp_url_jo)) {
            return MEDIA_RET_CJSON_ERROR;
        } else {
            strncpy(rrtr->rtmp_url, cJSON_GetStringValue(rtmp_url_jo), URL_LEN -1);
        }

        cJSON *recode_jo = cJSON_GetObjectItem(rtsp_to_rtmp_jo, "recode");
        if (!recode_jo || !cJSON_IsString(recode_jo)) {
            return MEDIA_RET_CJSON_ERROR;
        } else {
            strncpy(rrtr->recode, cJSON_GetStringValue(recode_jo), RESOLUTION_LEN -1);
        }
    }
    return 0;
}

int req_rtsp_to_rtmp_struct_to_msg(msg_processing_t *msgp, payload_t *payload,
                                   cJSON **obj)
{
    (void) msgp, (void)payload, (void)obj;
    *obj = NULL;
    return 0;
}

int req_rtsp_record_msg_to_do(msg_processing_t *msgp, response_t *resp)
{
    if (msgp == NULL || resp == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    req_rtsp_record_t * rrr = (req_rtsp_record_t *)msgp;

    resp->payload.result = media->rtsp_record(media, rrr->run, rrr->rtsp_url,
                                              rrr->out_path);
    return resp->payload.result;
}

int req_rtsp_record_msg_to_struct(msg_processing_t *msgp, cJSON *obj)
{
    req_rtsp_record_t *rrr = (req_rtsp_record_t *)msgp;

    cJSON *rtsp_record_jo = obj;

    cJSON *run_json_obj = cJSON_GetObjectItem(rtsp_record_jo, "run");
    if (!run_json_obj || !cJSON_IsBool(run_json_obj)) {
        return MEDIA_RET_CJSON_ERROR;
    } else {
        rrr->run = cJSON_IsTrue(run_json_obj);
    }

    if (rrr->run) {
        cJSON *rtsp_url_jo = cJSON_GetObjectItem(rtsp_record_jo, "rtsp_url");
        if (!rtsp_url_jo || !cJSON_IsString(rtsp_url_jo)) {
            return MEDIA_RET_CJSON_ERROR;
        } else {
            strncpy(rrr->rtsp_url, cJSON_GetStringValue(rtsp_url_jo), URL_LEN -1);
        }

        cJSON *out_path_jo = cJSON_GetObjectItem(rtsp_record_jo, "out_path");
        if (!out_path_jo || !cJSON_IsString(out_path_jo)) {
            return MEDIA_RET_CJSON_ERROR;
        } else {
            strncpy(rrr->out_path, cJSON_GetStringValue(out_path_jo), OUT_PATH_LEN -1 );
        }
    }
    return 0;
}

int req_rtsp_record_struct_to_msg(msg_processing_t *msgp, payload_t *payload, cJSON **obj)
{
    (void) msgp, (void)payload, (void)obj;
    *obj = NULL;
    return 0;
}

int req_rtsp_screenshot_msg_to_do(msg_processing_t *msgp, response_t *resp)
{
    if (msgp == NULL || resp == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    req_rtsp_screenshot_t * rrs = (req_rtsp_screenshot_t *)msgp;

    resp->payload.result = media->rtsp_screenshot(media, rrs->rtsp_url, rrs->out_path);
    return resp->payload.result;
}

int req_rtsp_screenshot_msg_to_struct(msg_processing_t *msgp, cJSON *obj)
{
    req_rtsp_screenshot_t *rrs = (req_rtsp_screenshot_t *)msgp;

    cJSON *rtsp_screenshot_jo = obj;

    cJSON *rtsp_url_jo = cJSON_GetObjectItem(rtsp_screenshot_jo, "rtsp_url");
    if (!rtsp_url_jo || !cJSON_IsString(rtsp_url_jo)) {
        return MEDIA_RET_CJSON_ERROR;
    } else {
        strncpy(rrs->rtsp_url, cJSON_GetStringValue(rtsp_url_jo), URL_LEN -1);
    }

    cJSON *out_path_jo = cJSON_GetObjectItem(rtsp_screenshot_jo, "out_path");
    if (!out_path_jo || !cJSON_IsString(out_path_jo)) {
        return MEDIA_RET_CJSON_ERROR;
    } else {
        strncpy(rrs->out_path, cJSON_GetStringValue(out_path_jo), OUT_PATH_LEN -1 );
    }
    return 0;
}

int req_rtsp_screenshot_struct_to_msg(msg_processing_t *msgp, payload_t *payload, cJSON **obj)
{
    (void) msgp, (void)payload, (void)obj;
    *obj = NULL;
    return 0;
}

int req_stream_probe_msg_to_do(msg_processing_t *msgp, response_t *resp)
{
    if (msgp == NULL || resp == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    req_stream_probe_t * rsp = (req_stream_probe_t *)msgp;
    resp_stream_probe_t *resp_data = &resp->payload.data.resp_stream_probe;

    resp->payload.result = media->stream_probe(media, rsp->rtsp_url, resp_data->info,
                                               STREAM_INFO_LEN);
    return resp->payload.result;
}

int req_stream_probe_msg_to_struct(msg_processing_t *msgp, cJSON *obj)
{
    req_stream_probe_t *rsp = (req_stream_probe_t *)msgp;

    cJSON *req_stream_probe_jo = obj;

    cJSON *rtsp_url_jo = cJSON_GetObjectItem(req_stream_probe_jo, "rtsp_url");
    if (!rtsp_url_jo || !cJSON_IsString(rtsp_url_jo)) {
        return MEDIA_RET_CJSON_ERROR;
    } else {
        strncpy(rsp->rtsp_url, cJSON_GetStringValue(rtsp_url_jo), URL_LEN -1);
    }
    return 0;
}

int req_stream_probe_struct_to_msg(msg_processing_t *msgp, payload_t *payload, cJSON **obj)
{
    if (!msgp || !payload || !obj) {
        return MEDIA_RET_NULL_PTR;
    }

    resp_stream_probe_t *resp_stream_probe = &payload->data.resp_stream_probe;
    cJSON *data = cJSON_CreateObject();
    *obj = data;

    cJSON *stream_probe_jo = cJSON_CreateString(resp_stream_probe->info);
    if (!stream_probe_jo) {
        goto exit;
    }
    cJSON_AddItemToObject(data, "info", stream_probe_jo);

    return 0;
exit:
    cJSON_Delete(data);
    *obj = NULL;
    return MEDIA_RET_CJSON_ERROR;
}


int req_user_cmd_msg_to_do(msg_processing_t *msgp, response_t *resp)
{
    if (msgp == NULL || resp == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    req_user_cmd_t * ruc = (req_user_cmd_t *)msgp;
    int ret = 0;

    ret = media->user_cmd(media, ruc->run, ruc->cmd);
    resp->payload.result = ret;

    return ret;
}

int req_user_cmd_msg_to_struct(msg_processing_t *msgp, cJSON *obj)
{
    req_user_cmd_t *ruc = (req_user_cmd_t *)msgp;

    cJSON *user_cmd_json_obj = obj;

    cJSON *run_json_obj = cJSON_GetObjectItem(user_cmd_json_obj, "run");
    if (!run_json_obj) {
        return MEDIA_RET_CJSON_ERROR;
    }
    if (cJSON_IsBool(run_json_obj)) {
        ruc->run = cJSON_IsTrue(run_json_obj);
    } else {
        return MEDIA_RET_CJSON_ERROR;
    }

    cJSON *cmd_json_obj = cJSON_GetObjectItem(user_cmd_json_obj, "cmd");
    if (!cmd_json_obj) {
        return MEDIA_RET_CJSON_ERROR;
    }
    if (cJSON_IsString(cmd_json_obj)) {
        strncpy(ruc->cmd, cJSON_GetStringValue(cmd_json_obj), CMD_LEN -1);
    } else {
        return MEDIA_RET_CJSON_ERROR;
    }
    return 0;
}

int req_user_cmd_struct_to_msg(msg_processing_t *msgp, payload_t *payload, cJSON **obj)
{
    (void) msgp, (void)payload, (void)obj;
    *obj = NULL;
    return 0;
}

int req_get_status_msg_to_do(msg_processing_t *msgp, response_t *resp)
{
    if (msgp == NULL || resp == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    media_status_t status;
    resp->payload.result = media->get_status(media, &status);
    resp->payload.data.resp_get_status.user_cmd = status.user_cmd_run;
    resp->payload.data.resp_get_status.rtsp_record = status.rtsp_record_run;
    resp->payload.data.resp_get_status.rtsp_to_rtmp = status.rtsp_to_rtmp_run;

    return resp->payload.result;
}

int req_get_status_msg_to_struct(msg_processing_t *msgp, cJSON *obj)
{
    (void )msgp, (void) obj;
    return 0;
}

int req_get_status_struct_to_msg(msg_processing_t *msgp, payload_t *payload, cJSON **obj)
{
    if (!msgp || !payload || !obj) {
        return MEDIA_RET_NULL_PTR;
    }

    resp_get_status_t *resp_get_status = &payload->data.resp_get_status;
    cJSON *data = cJSON_CreateObject();
    *obj = data;

    cJSON *rtsp_to_rtmp_jo = cJSON_CreateBool(resp_get_status->rtsp_to_rtmp);
    if (!rtsp_to_rtmp_jo) {
        goto exit;
    }
    cJSON_AddItemToObject(data, "rtsp_to_rtmp", rtsp_to_rtmp_jo);

    cJSON *rtsp_record_jo = cJSON_CreateBool(resp_get_status->rtsp_record);
    if (!rtsp_record_jo) {
        goto exit;
    }
    cJSON_AddItemToObject(data, "rtsp_record", rtsp_record_jo);

    cJSON *user_cmd_jo = cJSON_CreateBool(resp_get_status->user_cmd);
    if (!user_cmd_jo) {
        goto exit;
    }
    cJSON_AddItemToObject(data, "user_cmd", user_cmd_jo);

    return 0;
exit:
    cJSON_Delete(data);
    *obj = NULL;
    return MEDIA_RET_CJSON_ERROR;
}

int protocol_register_media(media_t *md)
{
    if (md == NULL) {
        return MEDIA_RET_NULL_PTR;
    }
    media = md;
    return 0;
}

int protocol_gen_resp_json_str(response_t *resp, cJSON *data, char *resp_str_buf, uint32_t size)
{
    if (!resp || !resp_str_buf) {
        return MEDIA_RET_NULL_PTR;
    }

    int ret = 0;
    cJSON *resp_root = cJSON_CreateObject();

    memset(resp_str_buf, 0, size);

    cJSON *cmd_json_obj = cJSON_CreateString(resp->cmd);
    if (!cmd_json_obj) {
        ret = MEDIA_RET_CJSON_ERROR;
        goto exit2;
    }
    cJSON_AddItemToObject(resp_root, "cmd", cmd_json_obj);

    cJSON *resp_payload_json_obj = cJSON_CreateObject();
    cJSON_AddItemToObject(resp_root, "payload", resp_payload_json_obj);

    cJSON *result_jo = cJSON_CreateNumber(resp->payload.result);
    if (!result_jo) {
        ret = MEDIA_RET_CJSON_ERROR;
        goto exit2;
    }
    cJSON_AddItemToObject(resp_payload_json_obj, "result", result_jo);

    if (strlen(resp->payload.error) > 0) {
        cJSON *error_jo = cJSON_CreateString(resp->payload.error);
        if (!error_jo) {
            ret = MEDIA_RET_CJSON_ERROR;
            goto exit2;
        }
        cJSON_AddItemToObject(resp_payload_json_obj, "error", error_jo);
    }

    if (data) {
        cJSON_AddItemToObject(resp_payload_json_obj, "data", data);
        data = NULL;
    }

    char *resp_json_str = cJSON_Print(resp_root);
    log_info("resp json: %s\n", resp_json_str);
    strncpy(resp_str_buf, resp_json_str, size - 1);
    free(resp_json_str);

exit2:
    cJSON_Delete(resp_root);
    if (data) {
        cJSON_Delete(data);
    }
    return ret;
}

int protocol_msg_handle(const void *in, void * out, uint32_t size)
{
    int ret = 0;
    request_t req;
    response_t resp;

    if (!in || !out) {
        ret = MEDIA_RET_INTERNAL_ERROR;
    }

    cJSON *req_json_obj = cJSON_Parse(in);
    if (!req_json_obj) {
        ret = MEDIA_RET_CJSON_ERROR;
        log_error("error: %s\n", protocol_get_error_str(ret));
        goto exit;
    }

    char *tttt = cJSON_Print(req_json_obj);
    log_info("recv json: \n%s\n", tttt);
    free(tttt);

    cJSON *req_cmd = cJSON_GetObjectItem(req_json_obj, "cmd");
    if (!req_cmd)  {
        ret = MEDIA_RET_CJSON_ERROR;
        log_error("error: %s\n", protocol_get_error_str(ret));
        goto exit;
    }

    if (cJSON_IsString(req_cmd)) {
        strncpy(req.cmd, cJSON_GetStringValue(req_cmd), CMD_LEN -1 );
    } else {
        ret = MEDIA_RET_CJSON_ERROR;
        log_error("error: %s\n", protocol_get_error_str(ret));
        goto exit;
    }

    cJSON *payload = cJSON_GetObjectItem(req_json_obj, "payload");
    if (!payload) {
        ret = MEDIA_RET_CJSON_ERROR;
        log_error("error: %s\n", protocol_get_error_str(ret));
        goto exit;
    }

    msg_processing_t *msgp = request_init(&req);
    if (!msgp) {
        log_error("msg_to_do error: %s\n", protocol_get_error_str(ret));
        return MEDIA_RET_CMD_NOT_SURPPORT;
    }

    ret = msgp->msg_to_struct(msgp, payload);
    if (ret) {
        log_error("msg_to_struct error: %s\n", protocol_get_error_str(ret));
        goto exit;
    }

    strncpy(resp.cmd, req.cmd, CMD_LEN - 1);
    ret = msgp->msg_to_do(msgp, &resp);
    if (ret) {
        log_error("msg_to_do error: %s\n", protocol_get_error_str(ret));
        goto exit;
    }

    cJSON *payload_data_jo = NULL;
    if (resp.payload.result == 0) {
        ret = msgp->struct_to_msg(msgp, &resp.payload, &payload_data_jo);
        if (ret) {
            log_error("struct_to_msg error: %s\n", protocol_get_error_str(ret));
            goto exit;
        }
    }

    ret = protocol_gen_resp_json_str(&resp, payload_data_jo, out, size);
    if (ret) {
        log_error("gen_resp_json_str error: %s\n", protocol_get_error_str(ret));
    }

exit:
    cJSON_Delete(req_json_obj);
    fflush(stdout);
    if (ret) {
        const char* format = "{\"cmd\":\"%s\",\"payload\":{\"result\":%d,\"error\":\"%s\"}}";
        snprintf(out, size - 1, format, req.cmd, ret, protocol_get_error_str(ret));
    }
    return strlen(out);
}

const char *protocol_get_error_str(media_ret_t ret)
{
    if (ret >= sizeof(error_str)) {
        return "";
    }
    return error_str[ret];
}
